package com.zuehlke.zegcamp14tuerschild; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import com.zuehlke.zegcamp14tuerschild.UpdateManager.RequestUpdateDataCallback; public class CommunicationService extends Service { private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private BluetoothDevice mBluetoothDevice; private Handler mHandler = new Handler(); private boolean mScanning = false; private BluetoothGattCharacteristic configCharacteristic; private BluetoothGattCharacteristic revisionNumberCharacteristic; private BluetoothGattCharacteristic personCharacteristic; //private String mBluetoothDeviceAddress = "6c00e78a-1b9c-4330-8731-54eb46af7c9f"; private String doorPlateServiceUUID = "13370000-4200-1000-8000-00805f9b34fb"; private String configCharacteristicUUID = "13370000-4201-1000-8000-00805f9b34fb"; private String revisionNumberCharacteristicUUID = "13370000-4202-1000-8000-00805f9b34fb"; private String personCharacteristicUUID = "13370000-4203-1000-8000-00805f9b34fb"; private short doorPlateID; private int doorPlateRevisionNumber; private LinkedList<String> namesToWrite = new LinkedList<String>(); private int BTLE_WRITE_FLAG = 0; private static final int BTLE_WRITE_OK = 0; private static final int BTLE_WRITE_ERROR = 1; private Map<String, Integer> discoveredDevices = new HashMap<String, Integer>(); //private String mBluetoothDeviceAddress = "ffffffff-ffff-ffff-ffff-fffffffffff0"; //UUID=13370000-4201-1000-8000-00805f9b34fb private BluetoothGatt mBluetoothGatt; //private int mConnectionState = STATE_DISCONNECTED; private final static String TAG = CommunicationService.class.getSimpleName(); @Override public IBinder onBind(Intent intent) { return null; } private List<UUID> parseUUIDs(final byte[] advertisedData) { List<UUID> uuids = new ArrayList<UUID>(); int offset = 0; while (offset < (advertisedData.length - 2)) { int len = advertisedData[offset++]; if (len == 0) break; int type = advertisedData[offset++]; switch (type) { case 0x02: // Partial list of 16-bit UUIDs case 0x03: // Complete list of 16-bit UUIDs while (len > 1) { int uuid16 = advertisedData[offset++]; uuid16 += (advertisedData[offset++] << 8); len -= 2; uuids.add(UUID.fromString(String.format( "%08x-0000-1000-8000-00805f9b34fb", uuid16))); } break; case 0x06:// Partial list of 128-bit UUIDs case 0x07:// Complete list of 128-bit UUIDs // Loop through the advertised 128-bit UUID's. while (len >= 16) { try { // Wrap the advertised bits and order them. ByteBuffer buffer = ByteBuffer.wrap(advertisedData, offset++, 16).order(ByteOrder.LITTLE_ENDIAN); long mostSignificantBit = buffer.getLong(); long leastSignificantBit = buffer.getLong(); uuids.add(new UUID(leastSignificantBit, mostSignificantBit)); } catch (IndexOutOfBoundsException e) { // Defensive programming. Log.e(TAG, e.toString()); continue; } finally { // Move the offset to read the next uuid. offset += 15; len -= 16; } } break; default: offset += (len - 1); break; } } return uuids; } @Override public int onStartCommand(Intent intent, int flags, int startId) { mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = mBluetoothManager.getAdapter(); // BluetoothManager. if (mBluetoothManager == null) { mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { Log.e(TAG, "Unable to initialize BluetoothManager."); return startId; } } mBluetoothAdapter = mBluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter."); return startId; } Log.i(TAG, "starting scan."); scanLeDevice(); return startId; } private void scanLeDevice() { // Stops scanning after a pre-defined scan period. mHandler.postDelayed(new Runnable() { @Override public void run() { // Get door plate with the strongest signal System.out.println("hashmap: " + discoveredDevices); Map.Entry<String, Integer> maxEntry = null; for (Map.Entry<String, Integer> entry : discoveredDevices.entrySet()) { if (maxEntry == null || entry.getValue().compareTo(maxEntry.getValue()) > 0) { maxEntry = entry; } } if (maxEntry != null) { updateRoomNameDisplay(maxEntry.getKey()); } discoveredDevices = new HashMap<String, Integer>(); Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { if (MainActivity.longRoomName != null && MainActivity.longRoomName.length() > 5 && MainActivity.userName != null && MainActivity.userName.length() > 0) { String plateId = MainActivity.longRoomName.substring(5); try { RESTManager.getInstance().requestSetLocation(CommunicationService.this, MainActivity.userName, Integer.parseInt(plateId)); } catch (NumberFormatException e) { e.printStackTrace(); } // TODO: handle error, retry } } }); if(mScanning) { mBluetoothAdapter.stopLeScan(bleScanCallback); mScanning = false; } new Handler().postDelayed(new Runnable() { @Override public void run() { scanLeDevice(); } }, 5000); } }, 10000); if(mBluetoothDevice == null) { mScanning = true; mBluetoothAdapter.startLeScan(bleScanCallback); } } private final BluetoothAdapter.LeScanCallback bleScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { List<UUID> scannedUUIDs = CommunicationService.this.parseUUIDs(scanRecord); //Log.i(TAG, "Devices found: "+scannedUUIDs); for (UUID uuid : scannedUUIDs) { if (uuid.toString().equals(doorPlateServiceUUID) /*&& device.getName().equals("rpi-kahe")*/) { discoveredDevices.put(device.getName(), rssi); // TODO: normally not an ID Log.i(TAG, "Device selected: "+uuid+" ("+device.getName()+") with RSSI "+rssi); mBluetoothDevice = device; mBluetoothAdapter.stopLeScan(bleScanCallback); mScanning = false; Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { Log.i(TAG, "run"); mBluetoothAdapter.cancelDiscovery(); mBluetoothGatt = mBluetoothDevice.connectGatt(CommunicationService.this, true, mGattCallback); } }); } } } }; private void writeNamesToConnectedPlate() { if(BTLE_WRITE_FLAG == BTLE_WRITE_OK) { if(namesToWrite.isEmpty()) { revisionNumberCharacteristic.setValue(doorPlateRevisionNumber, BluetoothGattCharacteristic.FORMAT_UINT32, 0); mBluetoothGatt.writeCharacteristic(revisionNumberCharacteristic); } else { String name = namesToWrite.pop(); System.out.println("Write person "+name); try { personCharacteristic.setValue(name.getBytes("ASCII")); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } mBluetoothGatt.writeCharacteristic(personCharacteristic); } } else { closeConnection(); } } private void closeConnection() { mBluetoothDevice = null; Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { mBluetoothGatt.close(); //mBluetoothAdapter = mBluetoothManager.getAdapter(); //mBluetoothAdapter.startLeScan(bleScanCallback); } }); } private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { //intentAction = ACTION_GATT_CONNECTED; //mConnectionState = STATE_CONNECTED; //broadcastUpdate(intentAction); Log.i(TAG, "Connected to GATT server."); //Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { //intentAction = ACTION_GATT_DISCONNECTED; //mConnectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); closeConnection(); } } @Override // New services discovered public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Log.i(TAG, "onServicesDiscovered received: " + status + " #services: " + gatt.getServices().size()); for (BluetoothGattService service : gatt.getServices()) { if (service.getUuid().toString().equals(doorPlateServiceUUID)) { // Save characteristics for (BluetoothGattCharacteristic characteristic :service.getCharacteristics()) { if (characteristic.getUuid().toString().equals(configCharacteristicUUID)) { configCharacteristic = characteristic; } else if (characteristic.getUuid().toString().equals(revisionNumberCharacteristicUUID)) { revisionNumberCharacteristic = characteristic; } else if (characteristic.getUuid().toString().equals(personCharacteristicUUID)) { personCharacteristic = characteristic; } } // Read revision number gatt.readCharacteristic(configCharacteristic); /*ByteBuffer buffer = ByteBuffer.allocate(2); buffer.putShort(Short.reverseBytes((short)42)); byte[] id = buffer.array(); byte[] roomName = "Raum 42".getBytes(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream( ); try { outputStream.write(id); outputStream.write(roomName); } catch (IOException e) { e.printStackTrace(); } byte payload[] = outputStream.toByteArray(); configCharacteristic.setValue(payload); gatt.writeCharacteristic(configCharacteristic);*/ } /*ByteBuffer buffer = ByteBuffer.allocate(2); buffer.putShort(Short.reverseBytes((short)1)); byte[] id = buffer.array(); byte[] roomName = "Super Raum".getBytes(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream( ); try { outputStream.write(id); outputStream.write(roomName); } catch (IOException e) { e.printStackTrace(); } byte payload[] = outputStream.toByteArray(); characteristic.setValue(payload); gatt.writeCharacteristic(characteristic);*/ } //broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override // Result of a characteristic read operation public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { //status = BluetoothGatt.GATT_SUCCESS; //characteristic = configCharacteristic; if (status == BluetoothGatt.GATT_SUCCESS) { if (characteristic == configCharacteristic) { Log.i(TAG, "config read: " + characteristic.getStringValue(0)); byte[] payload = characteristic.getValue(); byte[] idPayload = {payload[0], payload[1]}; doorPlateID = ByteBuffer.wrap(idPayload).order(ByteOrder.LITTLE_ENDIAN).getShort(); gatt.readCharacteristic(revisionNumberCharacteristic); } if (characteristic == revisionNumberCharacteristic) { final int connectedDoorPlateRevisionNumber = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0); Log.i(TAG, "revision number read: " + connectedDoorPlateRevisionNumber); //Looper.prepare(); Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { Log.d(TAG, "Doorplate id: "+doorPlateID); UpdateManager.getInstance().getUpdateDataForPlate(doorPlateID, new RequestUpdateDataCallback() { @Override public void onSuccess(JSONObject object) { try { if(object.getInt("updateId") > connectedDoorPlateRevisionNumber) {// TODO: remove = if there JSONArray names = object.getJSONArray("names"); doorPlateRevisionNumber = object.getInt("updateId"); for(int i=0;i<names.length();i++) { namesToWrite.add(names.getString(i)); } writeNamesToConnectedPlate(); } else { closeConnection(); } } catch (JSONException e) { e.printStackTrace(); } } }); } }); } } else { Log.i(TAG, "read unsuccessful: " + status); } } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { Log.i(TAG, "characteristic write status: "+status); if (status == BluetoothGatt.GATT_SUCCESS) { if (characteristic == configCharacteristic) { Log.i(TAG, "config write successful"); //closeConnection(); } else if (characteristic == revisionNumberCharacteristic) { Log.i(TAG, "revision number write successful"); Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { RESTManager.getInstance().requestSetUpdateStatus(CommunicationService.this, doorPlateRevisionNumber, "processed"); // TODO: handle error, retry } }); closeConnection(); } else if (characteristic == personCharacteristic) { Log.i(TAG, "person write successful"); writeNamesToConnectedPlate(); } } else { if (characteristic == configCharacteristic) { Log.i(TAG, "config write unsuccessful"); closeConnection(); } else if (characteristic == revisionNumberCharacteristic) { Log.i(TAG, "revision number write unsuccessful"); closeConnection(); // TODO: repeat later } else if (characteristic == personCharacteristic) { Log.i(TAG, "person write unsuccessful"); BTLE_WRITE_FLAG = BTLE_WRITE_ERROR; } } } }; @Override public void onDestroy() { Log.i(TAG, "destroyed."); super.onDestroy(); } private void updateRoomNameDisplay(String roomName) { Intent intent = new Intent(MainActivity.UPDATE_ROOM_NAME); intent.putExtra(MainActivity.EXTRA_ROOM_NAME, roomName); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } }